1   /*
2    * Copyright (c) 1998, 2001, Oracle and/or its affiliates. All rights reserved.
3    * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4    *
5    * This code is free software; you can redistribute it and/or modify it
6    * under the terms of the GNU General Public License version 2 only, as
7    * published by the Free Software Foundation.  Oracle designates this
8    * particular file as subject to the "Classpath" exception as provided
9    * by Oracle in the LICENSE file that accompanied this code.
10   *
11   * This code is distributed in the hope that it will be useful, but WITHOUT
12   * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13   * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14   * version 2 for more details (a copy is included in the LICENSE file that
15   * accompanied this code).
16   *
17   * You should have received a copy of the GNU General Public License version
18   * 2 along with this work; if not, write to the Free Software Foundation,
19   * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20   *
21   * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22   * or visit www.oracle.com if you need additional information or have any
23   * questions.
24   */
25  
26  import com.sun.javadoc.ClassDoc;
27  import com.sun.javadoc.MethodDoc;
28  import com.sun.javadoc.RootDoc;
29  import com.sun.javadoc.Tag;
30  
31  import java.beans.Introspector;
32  
33  import java.util.Enumeration;
34  import java.util.Hashtable;
35  import java.util.HashMap;
36  import java.util.StringTokenizer;
37  
38  /**
39   * Properties supported and tag syntax:
40   *
41   * @beaninfo
42   *      bound: flag
43   *      constrained: flag
44   *      expert: flag
45   *      hidden: flag
46   *      preferred: flag
47   *      description: string
48   *      displayname: string
49   *      propertyeditorclass: string (with dots: foo.bar.MyPropertyEditor
50   *      customizerclass: string (w/dots: foo.bar.MyCustomizer)
51   *      attribute: key1 value1
52   *      attribute: key2 value2
53   *
54   * TODO: getValue and genDocletInfo needs some cleaning.
55   *
56   * @author Hans Muller
57   * @author Rich Schiavi
58   * @author Mark Davidson
59   */
60  public class GenDocletBeanInfo {
61  
62      static String[] ATTRIBUTE_NAMES = { "bound",
63                                       "constrained",
64                                       "expert",
65                                       "hidden",
66                                       "preferred",
67                                       "displayname",
68                                       "propertyeditorclass",
69                                       "customizerclass",
70                                       "displayname",
71                                       "description",
72                                       "enum",
73                                       "attribute" };
74      private static boolean DEBUG = false;
75  
76      private static String fileDir = "";
77      private static String templateDir = "";
78  
79      public static final String TRUE = "true";
80      public static final String FALSE = "false";
81  
82      /**
83       * Method called from the javadoc environment to determint the options length.
84       * Doclet options:
85       *      -t template location
86       *      -d outputdir
87       *      -x true Enable debug output.
88       */
89      public static int optionLength(String option) {
90          // remind: this needs to be cleaned up
91          if (option.equals("-t"))
92              return 2;
93          if (option.equals("-d"))
94              return 2;
95          if (option.equals("-x"))
96              return 2;
97          return 0;
98      }
99  
100     /** @beaninfo
101      * bound:true
102      * constrained:false
103      * expert:true
104      * hidden:true
105      * preferred:false
106      * description: the description of this method can
107      *              do all sorts of funky things. if it \n
108      *              is indented like this, we have to remove
109      *              all char spaces greater than 2 and also any hard-coded \n
110      *              newline characters and all newlines
111      * displayname: theString
112      * propertyeditorclass: foo.bar.MyPropertyEditorClass
113      * customizerclass: foo.bar.MyCustomizerClass
114      * attribute:key1 value1
115      * attribute: key2  value2
116      *
117      */
118     public static boolean start(RootDoc doc) {
119         readOptions(doc.options());
120 
121         if (templateDir.length() == 0) {
122             System.err.println("-t option not specified");
123             return false;
124         }
125         if (fileDir.length() == 0) {
126             System.err.println("-d option not specified");
127             return false;
128         }
129 
130         GenSwingBeanInfo generator = new GenSwingBeanInfo(fileDir, templateDir, DEBUG);
131         Hashtable dochash = new Hashtable();
132         DocBeanInfo dbi;
133 
134         /* "javadoc Foo.java Bar.java" will return:
135         *         "Foo Foo.I1 Foo.I2 Bar Bar.I1 Bar.I2"
136         * i.e., with all the innerclasses of classes specified in the command
137         * line.  We don't want to generate BeanInfo for any of these inner
138         * classes, so we ignore these by remembering what the last outer
139         * class was.  A hack, I admit, but makes the build faster.
140         */
141         String previousClass = null;
142 
143         ClassDoc[] classes = doc.classes();
144 
145         for (int cnt = 0; cnt < classes.length; cnt++) {
146             String className = classes[cnt].qualifiedName();
147             if (previousClass != null &&
148                 className.startsWith(previousClass) &&
149                 className.charAt(previousClass.length()) == '.') {
150                 continue;
151             }
152             previousClass = className;
153 
154             // XXX - debug
155             System.out.println("\n>>> Generating beaninfo for " + className + "...");
156 
157             // Examine the javadoc tags and look for the the @beaninfo tag
158             // This first block looks at the javadoc for the class
159             Tag[] tags = classes[cnt].tags();
160             for (int i = 0; i < tags.length; i++) {
161                 if (tags[i].kind().equalsIgnoreCase("@beaninfo")) {
162                     if (DEBUG)
163                        System.out.println("GenDocletBeanInfo: found @beaninfo tagged Class: " + tags[i].text());
164                     dbi = genDocletInfo(tags[i].text(), classes[cnt].name());
165                     dochash.put(dbi.name, dbi);
166                     break;
167                 }
168             }
169 
170             // This block looks at the javadoc for the class methods.
171             int startPos = -1;
172             MethodDoc[] methods = classes[cnt].methods();
173             for (int j = 0; j < methods.length; j++) {
174                 // actually don't "introspect" - look for all
175                 // methods with a @beaninfo tag
176                 tags = methods[j].tags();
177                 for (int x = 0; x < tags.length; x++){
178                     if (tags[x].kind().equalsIgnoreCase("@beaninfo")){
179                         if ((methods[j].name().startsWith("get")) ||
180                             (methods[j].name().startsWith("set")))
181                             startPos = 3;
182                         else if (methods[j].name().startsWith("is"))
183                             startPos = 2;
184                         else
185                             startPos = 0;
186                         String propDesc =
187                             Introspector.decapitalize((methods[j].name()).substring(startPos));
188                         if (DEBUG)
189                             System.out.println("GenDocletBeanInfo: found @beaninfo tagged Method: " + tags[x].text());
190                         dbi = genDocletInfo(tags[x].text(), propDesc);
191                         dochash.put(dbi.name, dbi);
192                         break;
193                     }
194                 }
195             }
196             if (DEBUG) {
197                 // dump our classes doc beaninfo
198                 System.out.println(">>>>DocletBeanInfo for class: " + classes[cnt].name());
199                 Enumeration e = dochash.elements();
200                 while (e.hasMoreElements()) {
201                     DocBeanInfo db = (DocBeanInfo)e.nextElement();
202                     System.out.println(db.toString());
203                 }
204             }
205 
206             // Use the generator to create the beaninfo code for the class.
207             generator.genBeanInfo(classes[cnt].containingPackage().name(),
208                                         classes[cnt].name(), dochash);
209             // reset the values!
210             dochash.clear();
211         } // end for loop
212         return true;
213     }
214 
215     /**
216      * Reads the command line options.
217      * Side Effect, sets class variables templateDir, fileDir and DEBUG
218      */
219     private static void readOptions(String[][] options)  {
220         // Parse the command line args
221         for (int i = 0; i < options.length; i++){
222             if (options[i][0].equals("-t")) {
223                 templateDir = options[i][1];
224             } else if (options[i][0].equals("-d")) {
225                 fileDir = options[i][1];
226             } else if (options[i][0].equals("-x")){
227                 if (options[i][1].equals("true"))
228                     DEBUG=true;
229                 else
230                     DEBUG=false;
231             }
232         }
233     }
234 
235     /**
236      * Create a "BeanInfo" data structure from the tag. This is a data structure
237      * which contains all beaninfo data for a method or a class.
238      *
239      * @param text All the text after the @beaninfo tag.
240      * @param name Name of the property i.e., mnemonic for setMnemonic
241      */
242     private static DocBeanInfo genDocletInfo(String text, String name) {
243         int beanflags = 0;
244         String desc = "null";
245         String displayname = "null";
246         String propertyeditorclass = "null";
247         String customizerclass = "null";
248         String value = "null";
249         HashMap attribs = null;
250         HashMap enums = null;
251 
252         int index;
253 
254         for (int j = 0; j < ATTRIBUTE_NAMES.length; j++){
255             index = 0;
256             if ((index = text.indexOf(ATTRIBUTE_NAMES[j])) != -1){
257                 value = getValue((text).substring(index),ATTRIBUTE_NAMES[j]);
258 
259                 if (ATTRIBUTE_NAMES[j].equalsIgnoreCase("attribute")) {
260                     attribs = getAttributeMap(value, " ");
261                 }
262                 if (ATTRIBUTE_NAMES[j].equalsIgnoreCase("enum")) {
263                     enums = getAttributeMap(value, " \n");
264                 }
265                 else if (ATTRIBUTE_NAMES[j].equals("displayname")){
266                     displayname = value;
267                 }
268                 else if (ATTRIBUTE_NAMES[j].equalsIgnoreCase("propertyeditorclass")) {
269                     propertyeditorclass = value;
270                 }
271                 else if (ATTRIBUTE_NAMES[j].equalsIgnoreCase("customizerclass")){
272                     customizerclass = value;
273                 }
274                 else if ((ATTRIBUTE_NAMES[j].equalsIgnoreCase("bound"))
275                          && (value.equalsIgnoreCase(TRUE)))
276                     beanflags = beanflags | DocBeanInfo.BOUND;
277                 else if ((ATTRIBUTE_NAMES[j].equalsIgnoreCase("expert"))
278                          && (value.equalsIgnoreCase(TRUE)))
279                     beanflags = beanflags | DocBeanInfo.EXPERT;
280                 else if ((ATTRIBUTE_NAMES[j].equalsIgnoreCase("constrained"))
281                          && (value.equalsIgnoreCase(TRUE)))
282                     beanflags = beanflags | DocBeanInfo.CONSTRAINED;
283                 else if ((ATTRIBUTE_NAMES[j].equalsIgnoreCase("hidden"))
284                          && (value.equalsIgnoreCase(TRUE)))
285                     beanflags = beanflags | DocBeanInfo.HIDDEN;
286                 else if ((ATTRIBUTE_NAMES[j].equalsIgnoreCase("preferred"))
287                          && (value.equalsIgnoreCase(TRUE)))
288                     beanflags = beanflags | DocBeanInfo.PREFERRED;
289                 else if (ATTRIBUTE_NAMES[j].equalsIgnoreCase("description")){
290                     desc = value;
291                 }
292             }
293         }
294         /** here we create our doclet-beaninfo data structure, which we read in
295          *  later if it has anything worthwhile
296          */
297 
298         // Construct a new Descriptor class
299         return new DocBeanInfo(name, beanflags, desc,displayname,
300                                          propertyeditorclass, customizerclass,
301                                          attribs, enums);
302     }
303 
304     /**
305      * Parses the substring and returns the cleaned up value for the attribute.
306      * @param substring Full String of the attrib tag.
307      *       i.e., "attribute: visualUpdate true" will return "visualUpdate true";
308      */
309     private static String getValue(String substring, String prop) {
310         StringTokenizer t;
311         String value = "null";
312 
313         try {
314             /** if the ATTRIBUTE_NAMES is NOT the description, then we
315              *  parse until newline
316              *  if it is the description we read until the next token
317              *  and then look for a match in the last MAXMATCH index
318              *  and truncate the description
319              *  if it is the attribute we wead until no more
320              */
321             if (prop.equalsIgnoreCase("attribute")){
322                 StringBuffer tmp = new StringBuffer();
323                 try {
324                     t = new StringTokenizer(substring, " :\n");
325                     t.nextToken().trim();//the prop
326                     // we want to return : key1 value1 key2 value2
327                     while (t.hasMoreTokens()){
328                         tmp.append(t.nextToken().trim()).append(" ");
329                         tmp.append(t.nextToken().trim()).append(" ");
330                         String test = t.nextToken().trim();
331                         if (!(test.equalsIgnoreCase("attribute")))
332                             break;
333                     }
334                 } catch (Exception e){
335                 }
336                 value = tmp.toString();
337             }
338             else if (prop.equalsIgnoreCase("enum")){
339                 t = new StringTokenizer(substring, ":");
340                 t.nextToken().trim(); // the prop we already know
341                 StringBuffer tmp = new StringBuffer(t.nextToken().trim());
342                 for (int i = 0; i < ATTRIBUTE_NAMES.length; i++){
343                     if (tmp.toString().endsWith(ATTRIBUTE_NAMES[i])){
344                         int len = ATTRIBUTE_NAMES[i].length();
345                         // trim off that
346                         tmp.setLength(tmp.length() - len);
347                         break;
348                     }
349                 }
350                 value = tmp.toString();
351             }
352             else if (prop.equalsIgnoreCase("description")){
353                 t = new StringTokenizer(substring, ":");
354                 t.nextToken().trim(); // the prop we already know
355                 StringBuffer tmp = new StringBuffer(t.nextToken().trim());
356                 for (int i = 0; i < ATTRIBUTE_NAMES.length; i++){
357                     if (tmp.toString().endsWith(ATTRIBUTE_NAMES[i])){
358                         int len = ATTRIBUTE_NAMES[i].length();
359                         // trim off that
360                         tmp.setLength(tmp.length() - len);
361                         break;
362                     }
363                 }
364                 value = hansalizeIt(tmp.toString());
365             }
366             else {
367                 // Single value properties like bound: true
368                 t = new StringTokenizer(substring, ":\n");
369                 t.nextToken().trim(); // the prop we already know
370                 value = t.nextToken().trim();
371             }
372 
373             // now we need to look for a match of any of the
374             // property
375 
376             return value;
377         }
378         catch (Exception e){
379             return "invalidValue";
380         }
381     }
382 
383     /**
384      * Creates a HashMap containing the key value pair for the parsed values
385      * of the "attributes" and "enum" tags.
386      * ie. For attribute value: visualUpdate true
387      *     The HashMap will have key: visualUpdate, value: true
388      */
389     private static HashMap getAttributeMap(String str, String delim)  {
390         StringTokenizer t = new StringTokenizer(str, delim);
391         HashMap map = null;
392         String key;
393         String value;
394 
395         int num = t.countTokens()/2;
396         if (num > 0)  {
397             map = new HashMap();
398             for (int i = 0; i < num; i++) {
399                 key = t.nextToken().trim();
400                 value = t.nextToken().trim();
401                 map.put(key, value);
402             }
403         }
404         return map;
405     }
406 
407     // looks for extra spaces, \n hard-coded and invisible,etc
408     private static String hansalizeIt(String from){
409         char [] chars = from.toCharArray();
410         int len = chars.length;
411         int toss = 0;
412 
413         // remove double spaces
414         for (int i = 0; i < len; i++){
415             if ((chars[i] == ' ')) {
416                 if (i+1 < len) {
417                     if ((chars[i+1] == ' ' ) || (chars[i+1] == '\n'))
418                         {
419                             --len;
420                             System.arraycopy(chars,i+1,chars,i,len-i);
421                             --i;
422                         }
423                 }
424             }
425 
426             if (chars[i] == '\n'){
427                 chars[i] = ' ';
428                 i -= 2;
429             }
430 
431             if (chars[i] == '\\') {
432                 if (i+1 < len) {
433                     if (chars[i+1] == 'n'){
434                         chars[i+1] = ' ';
435                         --len;
436                         System.arraycopy(chars,i+1, chars,i, len-i);
437                         --i;
438                     }
439                 }
440             }
441         }
442         return new String(chars,0,len);
443     }
444 
445 }